/*
 * alu.c
 *
 *  Created on: Jan 21, 2013
 *      Author: Ian Smith, Travis Drake
 */

#include "alu.h"
#include "global.h"
#include <stdlib.h>

/*
 * Sets the status flags of the ALU after an operation has taken place.
 * alu: The ALU object to operate on.
 */
void alu_set_flags(AluPtr alu)
{
	unsigned short a = Register_getRegister(alu->operand_a);
	unsigned short b = Register_getRegister(alu->operand_b);
	unsigned short r = Register_getRegister(alu->result_r);

	Bit a_sign = (a & 0x8000) ? TRUE : FALSE;
	Bit b_sign = (b & 0x8000) ? TRUE : FALSE;
	Bit r_sign = (r & 0x8000) ? TRUE : FALSE;

	alu->status_n = (r & 0x8000) ? TRUE : FALSE;
	alu->status_z = (r == 0x0000) ? TRUE : FALSE;
	// alu->status_c = ??? // Now set in individual operations.
	alu->status_o = (a_sign == b_sign && a_sign != r_sign) ? TRUE : FALSE;
}

/*
 * Initializes a new ALU object.
 */
AluPtr alu_new_alu(void)
{
	AluPtr alu = (AluPtr)malloc(sizeof(AluStr));

	alu->operand_a = Register_newRegister();
	alu->operand_b = Register_newRegister();
	alu->result_r = Register_newRegister();
	alu->result_r2 = Register_newRegister();

	Register_setRegister(alu->operand_a, FALSE);
	Register_setRegister(alu->operand_b, FALSE);
	Register_setRegister(alu->result_r, FALSE);
	Register_setRegister(alu->result_r2, FALSE);

	return alu;
}

/*
 * Frees the given ALU object.
 * alu: The ALU object to free.
 */
void alu_free(AluPtr alu)
{
	Register_deleteRegister(alu->operand_a);
	Register_deleteRegister(alu->operand_b);
	Register_deleteRegister(alu->result_r);
	Register_deleteRegister(alu->result_r2);
	free(alu);
}

/*
 * Sets input operand A on the given ALU.
 * alu: The ALU object to set the input operand of.
 * value: The new value to set.
 */
void alu_set_operand_a(AluPtr alu, unsigned short value)
{
	Register_setRegister(alu->operand_a, value);
}

/*
 * Sets input operand B on the given ALU.
 * alu: The ALU object to set the input operand of.
 * value: The new value to set.
 */
void alu_set_operand_b(AluPtr alu, unsigned short value)
{
	Register_setRegister(alu->operand_b, value);
}

/*
 * Gets the result of the last ALU operation.
 * alu: The ALU object to get the result of.
 */
unsigned short alu_get_result_r(AluPtr alu)
{
	return Register_getRegister(alu->result_r);
}

/*
 * Gets the remainder result of the last ALU operation.
 * alu: The ALU object to get the remainder result of.
 */
unsigned short alu_get_result_r2(AluPtr alu)
{
	return Register_getRegister(alu->result_r2);
}

/*
 * Gets the N (negative) status bit for the last ALU operation.
 * alu: The ALU object to get the status of.
 */
Bit alu_get_status_n(AluPtr alu)
{
	return alu->status_n;
}

/*
 * Gets the Z (zero) status bit for the last ALU operation.
 * alu: The ALU object to get the status of.
 */
Bit alu_get_status_z(AluPtr alu)
{
	return alu->status_z;
}

/*
 * Gets the C (carry) status bit for the last ALU operation.
 * alu: The ALU object to get the status of.
 */
Bit alu_get_status_c(AluPtr alu)
{
	return alu->status_c;
}

/*
 * Gets the O (overflow) status bit for the last ALU operation.
 * alu: The ALU object to get the status of.
 */
Bit alu_get_status_o(AluPtr alu)
{
	return alu->status_o;
}

/*
 * Performs an addition operation with the two operands in the ALU.
 * alu: The ALU object to perform the operation on.
 */
void alu_add(AluPtr alu)
{
	unsigned short a = Register_getRegister(alu->operand_a);
	unsigned short b = Register_getRegister(alu->operand_b);
	unsigned short r = a + b;

	Register_setRegister(alu->result_r, r);
	Register_setRegister(alu->result_r2, 0);

	alu->status_c = (((unsigned int)a + (unsigned int)b) > 0xffff) ? TRUE : FALSE;
	alu_set_flags(alu);
}

/*
 * Performs a subtraction operation with the two operands in the ALU.
 * alu: The ALU object to perform the operation on.
 */
void alu_sub(AluPtr alu)
{
	unsigned short a = Register_getRegister(alu->operand_a);
	unsigned short b = Register_getRegister(alu->operand_b);
	unsigned short r = a - b;

	Register_setRegister(alu->result_r, r);
	Register_setRegister(alu->result_r2, 0);

	alu->status_c = FALSE;
	alu_set_flags(alu);
}

/*
 * Performs a multiplication operation with the two operands in the ALU.
 * alu: The ALU object to perform the operation on.
 */
void alu_mul(AluPtr alu)
{
	unsigned short a = Register_getRegister(alu->operand_a) & 0xff;
	unsigned short b = Register_getRegister(alu->operand_b) & 0xff;
	unsigned short r = a * b;

	Register_setRegister(alu->result_r, r);
	Register_setRegister(alu->result_r2, 0);

	alu->status_c = FALSE;
	alu_set_flags(alu);
}

/*
 * Performs a division operation with the two operands in the ALU.
 * alu: The ALU object to perform the operation on.
 */
void alu_div(AluPtr alu)
{
	unsigned short a = Register_getRegister(alu->operand_a);
	unsigned short b = Register_getRegister(alu->operand_b);

	if (b == 0)
	{
		// No architectural specification exists for this case, so we set the
		// result and remainder to 0xffff and set the overflow flag.
		Register_setRegister(alu->result_r, 0xffff);
		Register_setRegister(alu->result_r2, 0xffff);

		alu->status_c = FALSE;
		alu_set_flags(alu);
		alu->status_o = TRUE;
	}
	else
	{
		unsigned short r = a / b;
		unsigned short r2 = a % b;

		Register_setRegister(alu->result_r, r);
		Register_setRegister(alu->result_r2, r2);

		alu->status_c = FALSE;
		alu_set_flags(alu);
	}
}

/*
 * Performs a logical AND operation with the two operands in the ALU.
 * alu: The ALU object to perform the operation on.
 */
void alu_and(AluPtr alu)
{
	unsigned short a = Register_getRegister(alu->operand_a);
	unsigned short b = Register_getRegister(alu->operand_b);
	unsigned short r = a & b;

	Register_setRegister(alu->result_r, r);
	Register_setRegister(alu->result_r2, 0);

	alu->status_c = FALSE;
	alu_set_flags(alu);
}

/*
 * Performs a logical OR operation with the two operands in the ALU.
 * alu: The ALU object to perform the operation on.
 */
void alu_or(AluPtr alu)
{
	unsigned short a = Register_getRegister(alu->operand_a);
	unsigned short b = Register_getRegister(alu->operand_b);
	unsigned short r = a | b;

	Register_setRegister(alu->result_r, r);
	Register_setRegister(alu->result_r2, 0);

	alu->status_c = FALSE;
	alu_set_flags(alu);
}

/*
 * Performs a logical XOR operation with the two operands in the ALU.
 * alu: The ALU object to perform the operation on.
 */
void alu_xor(AluPtr alu)
{
	unsigned short a = Register_getRegister(alu->operand_a);
	unsigned short b = Register_getRegister(alu->operand_b);
	unsigned short r = a ^ b;

	Register_setRegister(alu->result_r, r);
	Register_setRegister(alu->result_r2, 0);

	alu->status_c = FALSE;
	alu_set_flags(alu);
}

/*
 * Performs a logical NOT operation with the two operands in the ALU.
 * alu: The ALU object to perform the operation on.
 */
void alu_not(AluPtr alu)
{
	unsigned short a = Register_getRegister(alu->operand_a);
	unsigned short r = ~a;

	Register_setRegister(alu->result_r, r);
	Register_setRegister(alu->result_r2, 0);

	alu->status_c = FALSE;
	alu_set_flags(alu);
}

/*
 * Performs a shift-left operation with the two operands in the ALU.
 * alu: The ALU object to perform the operation on.
 */
void alu_shl(AluPtr alu)
{
	unsigned short a = Register_getRegister(alu->operand_a);
	unsigned short r = a << 1;

	Register_setRegister(alu->result_r, r);
	Register_setRegister(alu->result_r2, 0);

	alu->status_c = (((unsigned int)a << 1) > 0xffff ? TRUE : FALSE);
	alu_set_flags(alu);
}

/*
 * Performs a shift-right operation with the two operands in the ALU.
 * alu: The ALU object to perform the operation on.
 */
void alu_shr(AluPtr alu)
{
	unsigned short a = Register_getRegister(alu->operand_a);
	unsigned short r = a >> 1;

	Register_setRegister(alu->result_r, r);
	Register_setRegister(alu->result_r2, 0);

	alu->status_c = FALSE;
	alu_set_flags(alu);
}
